#include "googlewiki.h"

#include <QtCore>
#include <QtGui/QAction>
#include <QtGui/QMenu>
#include <QtGui/QTextBrowser>
#include <QtGui/QToolBar>
#include <QtGui/QVBoxLayout>

GooglewikiPlugin::GooglewikiPlugin() : QObject(), JuffPlugin() {
	//	create actions and connect them to slots
	viewAction_ = new QAction("View Quickypad HTML", this);
	dateAction_ = new QAction("Current date", this);
	headerAction_ = new QAction("Add header", this);
	connect(viewAction_, SIGNAL(activated()), this, SLOT(view()));
	connect(dateAction_, SIGNAL(activated()), this, SLOT(insertDate()));
	connect(headerAction_, SIGNAL(activated()), this, SLOT(addHeader()));
	
	//	create menu
	menu_ = new QMenu("Googlewiki Plugin");
	menu_->addAction(viewAction_);
	menu_->addAction(dateAction_);
	menu_->addAction(headerAction_);

	//	create toolbar
	toolBar_ = new QToolBar("Googlewiki Plugin");

	//	You better specify the name, otherwise its position will 
	//	not be restored after application restart
	toolBar_->setObjectName("GooglewikiPluginToolBar");

	//	add 'View' action to toolbar
	toolBar_->addAction(viewAction_);
	
	//	create widget that will be embedded into dock 
	//	and put controls to it
	widget_ = new QWidget();
	view_ = new QTextBrowser();
	QVBoxLayout* vBox = new QVBoxLayout();
	vBox->addWidget(view_);
	widget_->setLayout(vBox);
	// ----
	setRulez();
}

GooglewikiPlugin::~GooglewikiPlugin() { 
}

QString GooglewikiPlugin::name() const {
//	Change this : your plugin name here
	return "Googlewiki Plugin";
}

void GooglewikiPlugin::init(QObject* handler) {
	//	Change this : add your SIGNAL/SLOT connections here
	//	this is the way how plugin interacts with main application
	//	See class DocHandler and its SIGNALS and SLOTS for details
	connect(this, SIGNAL(textRequest(QString&)), handler, SLOT(getText(QString&)));
	connect(this, SIGNAL(insertText(const QString&)), handler, SLOT(insertText(const QString&)));
	connect(this, SIGNAL(moveCursor(int, int)), handler, SLOT(setCursorPos(int, int)));
	connect(this, SIGNAL(requestCursorPos(int&, int&)), handler, SLOT(getCursorPos(int&, int&)));
}

void GooglewikiPlugin::deinit(QObject* handler) {
	//	Disconnect all SIGNAL/SLOT connections here. The best way is just to call
	//	  this->disconnect(handler);
	//	  handler->disconnect(this);
	//	if you have connection in both directions.
	//	Since here we have only connections between plugin's signals and 
	//	handler's slots, it's enough to call just one of them:	
	this->disconnect(handler);
}

QString GooglewikiPlugin::description() const {
//	Change this : you own description here
	return "This is a googlewiki plugin.";
}

QToolBar* GooglewikiPlugin::toolBar() const {
	return toolBar_;
}
QWidget* GooglewikiPlugin::settingsPage() const {
	//	return you settings widget (if you have one) here
	return 0;
}
QMenu* GooglewikiPlugin::menu() const {
	//	return you own menu (if you have one) here
	return menu_;
}
void GooglewikiPlugin::applySettings() {
}


QWidget* GooglewikiPlugin::dockWidget(Qt::DockWidgetArea& area) const {
	//	change widget's dock default location
	area = Qt::BottomDockWidgetArea;

	return widget_;
}

void GooglewikiPlugin::view() {
	//	request document text
	QString text, html;
	emit textRequest(text);

	// render wiki
	QStringList strm(text.split("\n"));
	for (int i = 0; i < strm.count(); i++) {
		html += formatLine(strm.value(i)) + "\n";
	}
	// Add an empty line, to close any open environment.
	html += formatLine(QString());
	//	update the text
	view_->setHtml(html);
	
	if (!widget_->isVisible()) {
		//	show widget's parent (dock) if it is not visible,
		if (widget_->parentWidget() != 0)
			widget_->parentWidget()->show();
	}
}

void GooglewikiPlugin::insertDate() {
	QDateTime dt = QDateTime::currentDateTime();
	emit insertText(dt.toString("hh:mm:ss dd-MM-yyyy"));
}

void GooglewikiPlugin::addHeader() {
	QString text = "/* This program was written to make everybody happy */\n";
	emit moveCursor(0, 0);
	emit insertText(text);
}

// ----
void GooglewikiPlugin::setRulez(void) {
	// Pre-formatted text.
	// A line that begins with a space is not formatted.
	// This rule has to be first on the list.
	reList_ << Rule("^[ ]", "", Pre);
	
	// Titles:
	// = My Title =
	// With an equal number of '=' on either side, determining the level.
	reList_ << Rule("^=([^=]*)=\\s*$", "<h1>\\1</h1>", Title);
	reList_ << Rule("^==([^=]*)==\\s*$", "<h2>\\1</h2>", Title);
	reList_ << Rule("^===([^=]*)===\\s*$", "<h3>\\1</h3>", Title);
	reList_ << Rule("^====([^=]*)====\\s*$", "<h4>\\1</h4>", Title);
	reList_ << Rule("^=====([^=]*)=====\\s*$", "<h5>\\1</h5>", Title);
	reList_ << Rule("^======([^=]*)======\\s*$", "<h6>\\1</h6>", Title);
	
	// Lists:
	// * Bullet list item
	// # Numbered list item
	// We use the first item on the capture list to determine the list level.
	reList_ << Rule("^([\\*\\#]+)(.*)$", "<li>\\2</li>", ListItem);
	
	// Links:
	// [[URL]] or [[URL|Link Text]]
	QString urlExp = "([^\\s\\(\\)\\[\\]|]+)";
	QString textExp = "([^\\]]+)";
	QString linkExp("\\[\\[" + urlExp + "\\]\\]");
	QString linkTextExp("\\[\\[" + urlExp + "\\s*\\|\\s*" + textExp
	                    + "\\]\\]");
	reList_ << Rule(linkExp, "<a href=\"\\1\">\\1</a>", Link);
	reList_ << Rule(linkTextExp, "<a href=\"\\1\">\\2</a>", Link);
	
	// Text formatting:
	// ''Italic'' or '''Bold'''.
	reList_ << Rule("\*([^']*)\*", "<b>\\1</b>", TextFormat);
	reList_ << Rule("_([^']*)_", "<i>\\1</i>", TextFormat);
	reList_ << Rule("~~([^']*)~~", "<s>\\1</s>", TextFormat);
	reList_ << Rule("\^\^([^']*)\^\^", "<sup>\\1</sup>", TextFormat);
	reList_ << Rule(",,([^']*),,", "<sub>\\1</sub>", TextFormat);
	reList_ << Rule("`([^']*)`", "<code>\\1</code>", TextFormat);

	// Blank line:
	// Ends a paragraph.
	reList_ << Rule("^\\s*$", "", Blank);
}

/**
 * Converts a single line of text from Wiki text to HTML.
 * The given line is matched against each rule in the rule list, and the
 * appropriate substitutions take place.
 * @param  line  A line of Wiki text
 * @return The line converted to HTML
 */
QString GooglewikiPlugin::formatLine(const QString& line)
{
	QString result;
	QList<Rule>::iterator itr;
	bool loop;
	Environment env;
	
	result = line;
	loop = true;
	env = Paragraph;
	
	// Apply each rule in sequence.
	for (itr = reList_.begin(); loop && itr != reList_.end(); ++itr) {
		if (!(*itr).apply(result))
			continue;

		switch ((*itr).role()) {
		case Pre:
			// Stop processing.
			env = PreBlock;
			loop = false;
			break;
			
		case Title:
			// Do not apply any other rules to titles.
			// TODO: Is this the correct behaviour?
			loop = false;
			env = None;
			break;

		case ListItem:
			// A list item.
			// Open/close list environments, as needed.
			result = adjustListLevel((*itr).captureList()[1]) + result;
			env = List;
			break;

		case Blank:
			// A blank line.
			// Resets the environment.
			env = None;
			break;
			
		default:
			;
		}
	}
	
	// Handle changes to the environment.
	// The order by which the commands inside this block are written matters!
	// We do not want to open an environment before closing one.
	if (env != env_) {
		// Open a new environment.
		switch (env) {
		case Paragraph:
			result = "<p>\n" + result;
			break;
			
		case PreBlock:
			result = "<pre>\n" + result;
			break;
			
		default:
			;
		}
		
		// Close an environment.
		switch (env_) {
		case Paragraph:
			result = "</p>\n" + result;
			break;
			
		case PreBlock:
			result = "</pre>\n" + result;
			break;
	
		case List:
			result = adjustListLevel("") + result;
			break;
			
		default:
			;
		}
		
		// Set the new environment.
		env_ = env;
	}
	
	return result;
}

/**
 * Compares a new list level to the existing one, and opens/closes environments
 * as required.
 * A list level is indicated by a string comprising '*' (bullet) and '#'
 * (number) characters. If the new string differs from the current one, than
 * the list environment changes, and the appropriate HTML tags need to be added.
 * @param
 */
QString GooglewikiPlugin::adjustListLevel(const QString& level)
{
	QString result;
	int minLength;
	int i, common;
	
	// Nothing to do if the new list level is the same as the current one.
	// Return an empty string.
	if (level == curListLevel_)
		return result;
	
	// Find the length of the common prefix of the two list strings.
	minLength = qMin(level.length(), curListLevel_.length());
	for (i = 0; (i < minLength) && (level.at(i) == curListLevel_.at(i)); i++)
		;
	common = i;
	
	// Close list environments that appear on the current list string, but not
	// on the new one.
	for (i = common; i < curListLevel_.length(); i++) {
		if (curListLevel_.at(i) == QChar('*'))
			result += "</ul>";
		else
			result += "</ol>";
	}
	
	// Open list environments that appear on the new list string, but not
	// on the current one.
	for (i = common; i < level.length(); i++) {
		if (level.at(i) == QChar('*'))
			result += "<ul>";
		else
			result += "<ol>";
	}
	
	// Set the new list level string.
	curListLevel_ = level;
	
	return result;
}

Q_EXPORT_PLUGIN2(plugin, GooglewikiPlugin)
